using FluentNHibernate.Mapping;
using NHibernateInsight.Tests.Domain;
using NHibernateInsight.Tests.Infrastructure;
using NUnit.Framework;

namespace NHibernateInsight.Tests.Insights.Relations.ManyToMany
{
    /// <summary>
    /// - You should define "inverse" on one side of relationship (on both sides it doesnt work!)
    /// - You should save many-to-many inside a transaction
    /// - If you will not have a cascade set, you will need to save both entities explicitly
    /// - If you will not have a cascade set, you will not need to add entities to both sides of relation
    /// - If you will have *any* cascade set, you will need to add child to parent, or on both sides (child to parent, parent to child)
    /// - If you will have save-update cascade set, you will not need to save both entities explicitly
    /// 
    /// When relation table is created, because of the bag collection, no primary key on both relation columns
    /// will be created. This will allow duplicate entries in the table
    /// </summary>
    [TestFixture]
    public class Saving_the_related_entites_when_entities_are_in_a_bag : TestContext
    {
        private class Mappings_of
        {
            public class Product : ClassMap<Domain.Product>
            {
                public Product()
                {
                    Id(x => x.Id)
                        .GeneratedBy.Native();
                    HasManyToMany(x => x.Prospects)
                        .Cascade.SaveUpdate()
                        .Inverse()
                        .AsBag();
                }
            }
            public class Product_not_being_the_inverse_side_of_relation : ClassMap<Domain.Product>
            {
                public Product_not_being_the_inverse_side_of_relation()
                {
                    Id(x => x.Id)
                        .GeneratedBy.Native();
                    HasManyToMany(x => x.Prospects)
                        .Cascade.SaveUpdate()
                        .AsBag();
                }
            }
            public class Product_not_having_cascade_on_relation : ClassMap<Domain.Product>
            {
                public Product_not_having_cascade_on_relation()
                {
                    Id(x => x.Id)
                        .GeneratedBy.Native();
                    HasManyToMany(x => x.Prospects)
                        .Cascade.None()
                        .AsBag();
                }
            }
            public class Product_having_all_cascade : ClassMap<Domain.Product>
            {
                public Product_having_all_cascade()
                {
                    Id(x => x.Id)
                        .GeneratedBy.Native();
                    HasManyToMany(x => x.Prospects)
                        .Cascade.All()
                        .Inverse()
                        .AsBag();
                }
            }
            public class Product_having_delete_cascade : ClassMap<Domain.Product>
            {
                public Product_having_delete_cascade()
                {
                    Id(x => x.Id)
                        .GeneratedBy.Native();
                    HasManyToMany(x => x.Prospects)
                        .Cascade.Delete()
                        .Inverse()
                        .AsBag();
                }
            }
            public class Prospect : ClassMap<Domain.Prospect>
            {
                public Prospect()
                {
                    Id(x => x.Id)
                        .GeneratedBy.Native();
                    HasManyToMany(x => x.Products)
                        .Cascade.SaveUpdate()
                        .AsBag();
                }
            }
            public class Prospect_not_having_cascade_on_relation : ClassMap<Domain.Prospect>
            {
                public Prospect_not_having_cascade_on_relation()
                {
                    Id(x => x.Id)
                        .GeneratedBy.Native();
                    HasManyToMany(x => x.Products)
                        .Cascade.None()
                        .AsBag();
                }
            }
            public class Prospect_having_all_cascade : ClassMap<Domain.Prospect>
            {
                public Prospect_having_all_cascade()
                {
                    Id(x => x.Id)
                        .GeneratedBy.Native();
                    HasManyToMany(x => x.Products)
                        .Cascade.All()
                        .AsBag();
                }
            }
            public class Prospect_having_delete_cascade : ClassMap<Domain.Prospect>
            {
                public Prospect_having_delete_cascade()
                {
                    Id(x => x.Id)
                        .GeneratedBy.Native();
                    HasManyToMany(x => x.Products)
                        .Cascade.Delete()
                        .AsBag();
                }
            }
            public class Prospect_with_inverse : ClassMap<Domain.Prospect>
            {
                public Prospect_with_inverse()
                {
                    Id(x => x.Id)
                        .GeneratedBy.Native();
                    HasManyToMany(x => x.Products)
                        .Cascade.SaveUpdate()
                        .Inverse()
                        .AsBag();
                }
            }
        }

        [SetUp]
        public void Given_that()
        {
            PrepareForSql2005ExpressDb();


            HideCreateAndDropDbSqlFromConsole();
            HideCreateAndDropSchemaSqlFromConsole();
        }

        [TearDown]
        public void Cleanup_database()
        {
            Cleanup();
        }

        /// <summary>
        /// Then no relation will be saved (only entities themselves)
        /// </summary>
        [Test]
        public void When_saving_both_Entities_in_relation_without_transaction()
        {
            Init(m => m.FluentMappings
                          .Add<Mappings_of.Product>()
                          .Add<Mappings_of.Prospect>());


            var product = new Product();
            var prospect = new Prospect();

            product.Prospects.Add(prospect);
            prospect.Products.Add(product);


            CurrentSession.SaveOrUpdate(product);
            CurrentSession.SaveOrUpdate(prospect);
        }

        /// <summary>
        /// Then relation and entities will be saved
        /// </summary>
        [Test]
        public void When_saving_both_Entities_in_relation_within_transaction()
        {
            Init(m => m.FluentMappings
              .Add<Mappings_of.Product>()
              .Add<Mappings_of.Prospect>());


            var product = new Product();
            var prospect = new Prospect();

            product.Prospects.Add(prospect);
            prospect.Products.Add(product);

            using (var tran = CurrentSession.BeginTransaction())
            {
                CurrentSession.SaveOrUpdate(product);
                CurrentSession.SaveOrUpdate(prospect);

                tran.Commit();
            }
        }

        /// <summary>
        /// Then a duplicate Insert to the relation table will occur
        /// </summary>
        [Test]
        public void When_saving_both_Entities_in_relation_without_any_of_them_being_the_inverse_side_of_relationship()
        {
            Init(m => m.FluentMappings
                          .Add<Mappings_of.Product_not_being_the_inverse_side_of_relation>()
                          .Add<Mappings_of.Prospect>());


            var product = new Product();
            var prospect = new Prospect();

            product.Prospects.Add(prospect);
            prospect.Products.Add(product);

            using (var tran = CurrentSession.BeginTransaction())
            {
                CurrentSession.SaveOrUpdate(product);

                tran.Commit();
            }
        }

        /// <summary>
        /// Then no relation will be saved (only entities themselves)
        /// </summary>
        [Test]
        public void When_saving_both_Entities_in_relation_and_connecting_the_relation_on_child_side_only_having_saveupdate_cascade()
        {
            Init(m => m.FluentMappings
                          .Add<Mappings_of.Product>()
                          .Add<Mappings_of.Prospect>());


            var product = new Product();
            var prospect = new Prospect();

            product.Prospects.Add(prospect);

            using (var tran = CurrentSession.BeginTransaction())
            {
                CurrentSession.SaveOrUpdate(product);
                CurrentSession.SaveOrUpdate(prospect);

                tran.Commit();
            }

        }

        /// <summary>
        /// Then no relation will be saved (only entities themselves). 
        /// Notice, that we've only saved one entity...but the other is saved automatically.
        /// </summary>
        [Test]
        public void When_saving_child_inverse_side_of_relation_and_connecting_the_relation_on_child_side_only_having_saveupdate_cascade()
        {
            Init(m => m.FluentMappings
                          .Add<Mappings_of.Product>()
                          .Add<Mappings_of.Prospect>());


            var product = new Product();
            var prospect = new Prospect();

            product.Prospects.Add(prospect);

            using (var tran = CurrentSession.BeginTransaction())
            {
                CurrentSession.SaveOrUpdate(product);

                tran.Commit();
            }

        }

        /// <summary>
        /// Then exception will occur, saying that the other side was not saved and we are referencing it.
        /// </summary>
        [Test]
        public void When_saving_child_inverse_side_of_relation_and_connecting_the_relation_on_child_side_only_and_not_having_cascade()
        {
            Init(m => m.FluentMappings
                          .Add<Mappings_of.Product_not_having_cascade_on_relation>()
                          .Add<Mappings_of.Prospect_not_having_cascade_on_relation>());



            var product = new Product();
            var prospect = new Prospect();

            product.Prospects.Add(prospect);

            using (var tran = CurrentSession.BeginTransaction())
            {
                CurrentSession.SaveOrUpdate(product);

                tran.Commit();
            }

        }

        /// <summary>
        /// Then relation and entities will be saved
        /// </summary>
        [Test]
        public void When_saving_both_Entities_in_relation_and_connecting_the_relation_on_parent_side_only_having_saveupdate_cascade()
        {
            Init(m => m.FluentMappings
                          .Add<Mappings_of.Product>()
                          .Add<Mappings_of.Prospect>());


            var product = new Product();
            var prospect = new Prospect();

            prospect.Products.Add(product);

            using (var tran = CurrentSession.BeginTransaction())
            {
                CurrentSession.SaveOrUpdate(product);
                CurrentSession.SaveOrUpdate(prospect);

                tran.Commit();
            }

        }

        /// <summary>
        /// Then relation and entities will be saved
        /// </summary>
        [Test]
        public void When_saving_parent_side_of_relation_and_connecting_the_relation_on_parent_side_only_having_saveupdate_cascade()
        {
            Init(m => m.FluentMappings
                          .Add<Mappings_of.Product>()
                          .Add<Mappings_of.Prospect>());


            var product = new Product();
            var prospect = new Prospect();

            prospect.Products.Add(product);

            using (var tran = CurrentSession.BeginTransaction())
            {
                CurrentSession.SaveOrUpdate(prospect);

                tran.Commit();
            }

        }

        /// <summary>
        /// Then exception will occur, saying that the other side was not saved and we are referencing it.
        /// </summary>
        [Test]
        public void When_saving_parent_side_of_relation_and_connecting_the_relation_on_parent_side_only_and_not_having_cascade()
        {
            Init(m => m.FluentMappings
                          .Add<Mappings_of.Product_not_having_cascade_on_relation>()
                          .Add<Mappings_of.Prospect_not_having_cascade_on_relation>());



            var product = new Product();
            var prospect = new Prospect();

            prospect.Products.Add(product);

            using (var tran = CurrentSession.BeginTransaction())
            {
                CurrentSession.SaveOrUpdate(prospect);

                tran.Commit();
            }

        }

        /// <summary>
        /// Then only parent side of the relationship will be saved
        /// </summary>
        [Test]
        public void When_saving_parent_side_of_relation_and_connecting_the_relation_on_child_inverse_side_only_having_saveupdate_cascade()
        {
            Init(m => m.FluentMappings
                          .Add<Mappings_of.Product>()
                          .Add<Mappings_of.Prospect>());


            var product = new Product();
            var prospect = new Prospect();

            product.Prospects.Add(prospect);

            using (var tran = CurrentSession.BeginTransaction())
            {
                CurrentSession.SaveOrUpdate(prospect);

                tran.Commit();
            }

        }

        /// <summary>
        /// Then only parent side of the relationship will be saved
        /// </summary>
        [Test]
        public void When_saving_parent_side_of_relation_and_connecting_the_relation_on_child_inverse_side_only_and_not_having_cascade()
        {
            Init(m => m.FluentMappings
                          .Add<Mappings_of.Product_not_having_cascade_on_relation>()
                          .Add<Mappings_of.Prospect_not_having_cascade_on_relation>());



            var product = new Product();
            var prospect = new Prospect();

            product.Prospects.Add(prospect);

            using (var tran = CurrentSession.BeginTransaction())
            {
                CurrentSession.SaveOrUpdate(prospect);

                tran.Commit();
            }

        }

        /// <summary>
        /// Then only child side of the relationship will be saved
        /// </summary>
        [Test]
        public void When_saving_child_inverse_side_of_relation_and_connecting_the_relation_on_parent_side_only_having_saveupdate_cascade()
        {
            Init(m => m.FluentMappings
                          .Add<Mappings_of.Product>()
                          .Add<Mappings_of.Prospect>());


            var product = new Product();
            var prospect = new Prospect();

            prospect.Products.Add(product);

            using (var tran = CurrentSession.BeginTransaction())
            {
                CurrentSession.SaveOrUpdate(product);

                tran.Commit();
            }

        }

        /// <summary>
        /// Then only child side of the relationship will be saved
        /// </summary>
        [Test]
        public void When_saving_child_inverse_side_of_relation_and_connecting_the_relation_on_parent_side_only_and_not_having_cascade()
        {
            Init(m => m.FluentMappings
                          .Add<Mappings_of.Product_not_having_cascade_on_relation>()
                          .Add<Mappings_of.Prospect_not_having_cascade_on_relation>());



            var product = new Product();
            var prospect = new Prospect();

            prospect.Products.Add(product);

            using (var tran = CurrentSession.BeginTransaction())
            {
                CurrentSession.SaveOrUpdate(product);

                tran.Commit();
            }

        }        
        
        /// <summary>
        /// Then both entities and their relation will be saved.
        /// Notice, that this is only happening because there is no cascade defined
        /// (see "When_saving_related_Entities_and_not_connecting_the_relation_on_both_sides_but_having_saveupdate_cascade" 
        ///     how it doesn't happen where there is a defined cascade)
        /// </summary>
        [Test]
        public void When_saving_both_Entities_in_relation_and_connecting_the_relation_on_child_side_and_not_having_cascade()
        {
            Init(m => m.FluentMappings
                          .Add<Mappings_of.Product_not_having_cascade_on_relation>()
                          .Add<Mappings_of.Prospect_not_having_cascade_on_relation>());



            var product = new Product();
            var prospect = new Prospect();

            product.Prospects.Add(prospect);

            using (var tran = CurrentSession.BeginTransaction())
            {
                CurrentSession.SaveOrUpdate(product);
                CurrentSession.SaveOrUpdate(prospect);

                tran.Commit();
            }
        }

        /// <summary>
        /// Then no relation will be saved (only entities themselves)
        /// </summary>
        [Test]
        public void When_saving_both_Entities_in_relation_and_connecting_the_relation_on_child_side_and_having_all_cascade()
        {
            Init(m => m.FluentMappings
                          .Add<Mappings_of.Product_having_all_cascade>()
                          .Add<Mappings_of.Prospect_having_all_cascade>());



            var product = new Product();
            var prospect = new Prospect();

            product.Prospects.Add(prospect);

            using (var tran = CurrentSession.BeginTransaction())
            {
                CurrentSession.SaveOrUpdate(product);
                CurrentSession.SaveOrUpdate(prospect);

                tran.Commit();
            }
        }

        /// <summary>
        /// Then no relation will be saved (only entities themselves)
        /// </summary>
        [Test]
        public void When_saving_both_Entities_in_relation_and_connecting_the_relation_on_child_side_and_having_a_delete_cascade()
        {
            Init(m => m.FluentMappings
                          .Add<Mappings_of.Product_having_delete_cascade>()
                          .Add<Mappings_of.Prospect_having_delete_cascade>());



            var product = new Product();
            var prospect = new Prospect();

            product.Prospects.Add(prospect);

            using (var tran = CurrentSession.BeginTransaction())
            {
                CurrentSession.SaveOrUpdate(product);
                CurrentSession.SaveOrUpdate(prospect);

                tran.Commit();
            }
        }

        /// <summary>
        /// Then relation and entities will be saved
        /// </summary>
        [Test]
        public void When_saving_both_Entities_in_relation_and_connecting_the_relation_on_parent_side_and_having_all_cascade()
        {
            Init(m => m.FluentMappings
                          .Add<Mappings_of.Product_having_all_cascade>()
                          .Add<Mappings_of.Prospect_having_all_cascade>());



            var product = new Product();
            var prospect = new Prospect();

            prospect.Products.Add(product);

            using (var tran = CurrentSession.BeginTransaction())
            {
                CurrentSession.SaveOrUpdate(product);
                CurrentSession.SaveOrUpdate(prospect);

                tran.Commit();
            }
        }

        /// <summary>
        /// Then relation and entities will be saved
        /// </summary>
        [Test]
        public void When_saving_both_Entities_in_relation_and_connecting_the_relation_on_parent_side_and_having_a_delete_cascade()
        {
            Init(m => m.FluentMappings
                          .Add<Mappings_of.Product_having_delete_cascade>()
                          .Add<Mappings_of.Prospect_having_delete_cascade>());



            var product = new Product();
            var prospect = new Prospect();

            prospect.Products.Add(product);

            using (var tran = CurrentSession.BeginTransaction())
            {
                CurrentSession.SaveOrUpdate(product);
                CurrentSession.SaveOrUpdate(prospect);

                tran.Commit();
            }
        }

        /// <summary>
        /// Then no relation will be saved (only entities themselves)
        /// </summary>
        [Test]
        public void When_saving_both_Entities_in_relation_and_connecting_the_relation_on_both_sides_and_both_entities_have_inverse_set()
        {
            Init(m => m.FluentMappings
                          .Add<Mappings_of.Product>()
                          .Add<Mappings_of.Prospect_with_inverse>());



            var product = new Product();
            var prospect = new Prospect();

            prospect.Products.Add(product);
            product.Prospects.Add(prospect);

            using (var tran = CurrentSession.BeginTransaction())
            {
                CurrentSession.SaveOrUpdate(product);
                CurrentSession.SaveOrUpdate(prospect);

                tran.Commit();
            }
        }

        /// <summary>
        /// Then no relation will be saved (only entities themselves)
        /// </summary>
        [Test]
        public void When_saving_both_Entities_in_relation_and_connecting_the_relation_on_one_side_and_both_entities_have_inverse_set()
        {
            Init(m => m.FluentMappings
                          .Add<Mappings_of.Product>()
                          .Add<Mappings_of.Prospect_with_inverse>());


            var product = new Product();
            var prospect = new Prospect();

            product.Prospects.Add(prospect);

            using (var tran = CurrentSession.BeginTransaction())
            {
                CurrentSession.SaveOrUpdate(product);
                CurrentSession.SaveOrUpdate(prospect);

                tran.Commit();
            }
        }

        /// <summary>
        /// Then no relation will be saved (only entities themselves)
        /// </summary>
        [Test]
        public void When_saving_both_Entities_in_relation_and_connecting_the_relation_on_opposite_side_and_both_entities_have_inverse_set()
        {
            Init(m => m.FluentMappings
                          .Add<Mappings_of.Product>()
                          .Add<Mappings_of.Prospect_with_inverse>());


            var product = new Product();
            var prospect = new Prospect();

            prospect.Products.Add(product);

            using (var tran = CurrentSession.BeginTransaction())
            {
                CurrentSession.SaveOrUpdate(product);
                CurrentSession.SaveOrUpdate(prospect);

                tran.Commit();
            }
        }

    }
}